// 十二、TypeScript 泛型
// 软件工程中，我们不仅要创建一致的定义良好的 API，同时也要考虑可重用性。 组件不仅能够支持当前的数据类型，同时也能支持未来的数据类型，这在创建大型系统时为你提供了十分灵活的功能。

import { createVoid } from "typescript";

// 在像 C# 和 Java 这样的语言中，可以使用泛型来创建可重用的组件，一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

// 设计泛型的关键目的是在成员之间提供有意义的约束，这些成员可以是：类的实例成员、类的方法、函数参数和函数返回值。

// 泛型（Generics）是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型，使用泛型来创建可复用的组件要更好，因为泛型会保留参数类型。

// 12.1 泛型接口
// interface IGenericClass<T>{
//     method1:(x:T)=>(T);
// }

// // 12.2 泛型类
// class GenericClass<T> implements IGenericClass<T>{
//     filed1:T;
//     // 实现接口的方法
//     method1:(x:T)=>(T)=(x)=>{
//         return x;
//     }
// }

// 12.3 泛型变量
// 对刚接触 TypeScript 泛型的小伙伴来说，看到 T 和 E，还有 K 和 V 这些泛型变量时，估计会一脸懵逼。其实这些大写字母并没有什么本质的区别，只不过是一个约定好的规范而已。也就是说使用大写字母 A-Z 定义的类型变量都属于泛型，把 T 换成 A，也是一样的。下面我们介绍一下一些常见泛型变量代表的意思：

// T（Type）：表示一个 TypeScript 类型
// K（Key）：表示对象中的键类型
// V（Value）：表示对象中的值类型
// E（Element）：表示元素类型

// 12.4 泛型工具类型
// 为了方便开发者 TypeScript 
// 内置了一些常用的工具类型，比如 Partial、Required、Readonly、Record 和 ReturnType 等。
// 出于篇幅考虑，这里我们只简单介绍 Partial 工具类型。不过在具体介绍之前，
// 我们得先介绍一些相关的基础知识，方便读者自行学习其它的工具类型。

// 1.typeof

// 在 TypeScript 中，typeof 操作符可以用来获取一个变量声明或对象的类型。

const Read: (x: number) => void = (x) => {
    console.log(x);
}
type Read_Type = typeof Read;


interface IPrint<T> {
    Print: (x: T) => void
    Read: Read_Type;
}

class Print<T> implements IPrint<T>{
    Print: (x: T) => void = (x) => console.log(x); // 软件工程中，我们不仅要创建一致的定义良好的 API，同时也要考虑可重用性。 组件不仅能够支持当前的数据类型，同时也能支持未来的数据类型，这在创建大型系统时为你提供了十分灵活的功能。
    Read: (x: number) => void = (x) => { console.log(x) }
}

let print1 = new Print<number>;
console.log(print1.Print(123));

type print1_type = typeof print1; //Print<number>
console.log(typeof print1);

let print2: Print<string> = {
    Print: (x: string) => console.log(x),
    Read: (x:number)=>console.log(x)
    // Read: function (x: number): void {
    //     throw new Error("Function not implemented.");
    // }
}
type print2_type = typeof print2; // Print<string>
console.log(typeof print2);

// interface Person {
//     name: string;
//     age: number;
//   }
//
//   const sem: Person = { name: 'semlinker', age: 30 };
//   type Sem= typeof sem; // -> Person
//
//   function toArray(x: number): Array<number> {
//     return [x];
//   }
//
//   type Func = typeof toArray; // -> (x: number) => number[]


// 2.keyof
// keyof 操作符可以用来一个对象中的所有 key 值：

 ​ interface Person {
     name: string;
     age: number;   
 }
 ​
 type K1 = keyof Person; // "name" | "age"

 type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 


//  如果这个类型有一个 string 或者 number 类型的索引签名，keyof 则会直接返回这些类型：
//  索性签名 代表这个类素有的KEY 为string类型，但是为什么结果里面包含number呢，因为 JS的特性决定例如 obj[0] ==obu['0']
type K3 = keyof { [x: string]: Person|string|number|symbol,name:string ,age:number};  // string | number 
type K3_2 = keyof { name:string ,age:number};  // "name" | "age"
 


//  如果这个类型有一个 string 或者 number 类型的索引签名，keyof 则会直接返回这些类型：
//  索性签名 代表这个类素有的KEY 为string类型，但是为什么结果里面包含number呢，因为 JS的特性决定例如 obj[0] ==obu['0']
let thisobj:{ [x: string]: Person } = {"szq":{name:"szq1",age:33},1:{name:"szq2",age:33}};
type K4 = keyof typeof thisobj;
console.log(typeof thisobj);



 type Mapish = { [k: string]: boolean };
 type M = keyof Mapish;
 // type M = string | number


//  http://3.in

// in 用来遍历枚举类型：

//  ​
//  type Keys = "a" | "b" | "c"
//  ​
//  type Obj =  {
//    [p in Keys]: any
//  } // -> { a: any, b: any, c: any }

 type arrayType=[string ,string ,string ,string];
 let array:arrayType=["a","b","c","d"]
type typeofarray =typeof array;
type keyoftypeofarray =keyof typeofarray;


type obj2_type={
    [key in keyof Person]:string
}



// 4.infer
// 在TypeScript中，infer是一个关键字，用于在条件类型（conditional types）中推断（infer）类型参数。
// 通过使用infer关键字，我们可以在类型系统中进行类型的自动推断，从而实现更加灵活和可复用的类型定义。

// 在条件类型语句中，可以用 infer 声明一个类型变量并且对它进行使用。

// 如果泛型T是()=> infer R的子集，则返回infer R获取到的类型，否则返回boolean
type Func<T> = T extends () => infer R ? R : boolean;
let func1: Func<number>; // boolean;
// type Func<number> = number extends () => infer number ? number : boolean;

let func2: Func<''>; // boolean
let func3: Func<() => Promise<number>>; // Promise<number>
let func4: Func<() => ''>; // ''

 ​
 type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
//  理解为：如果 T 继承了 (...args: any[]) => any 类型，则返回类型 R，否则返回 any。
// 其中 R 是什么呢？
// R 被定义在 extends (...args: any[]) => infer R 中，即 R 是从传入参数类型中推导出来的。

//？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？
// 强哥的理解： 首先判断T是否符合规则，是不是这种类型 --> (...args: any[]) => infer R ； 
// 符合规则的同时 把参数T中的返回类型放入R中。 
// 例如：() => Promise<number> 的返回类型是Promise<number> ，所以R代表的类型就是Promise<number>
// 这里可能感觉奇怪，参数T可以是一个基本类型，也可以是一个函数类型，委托类型
// 如果符合规则就返回R
// 如果不符合规则就返回any
//  注意这个规则是自己定义的。只有符合规则才能给R赋值，所以貌似R 只能放在true分支中
//？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？？

 let a1:ReturnType<()=>void>;// aa:void

 let a2:ReturnType<()=>string>;// a2:string

 let a3:ReturnType<(a:boolean)=>number>;// a3:number

// 以上代码中 infer R 就是声明一个变量来承载传入函数签名的返回值类型，
// 简单说就是用它取到函数返回值的类型方便之后使用。

// type Obj<T> = T extends { a: infer VT, b: infer VT } ? VT : number;
type Obj<T> = T extends { a: infer VT1, b: infer VT2 ,c: infer VT3  } ? VT1|VT2|VT3 : number;

let obj1: Obj<string>;  // number;
let obj2: Obj<true>;  // number;
let obj3: Obj<{a: string, b: string}>;   // string
let obj4: Obj<{a: number, b: string, c: symbol}>;   // string | number|symbol
// 当a、b为不同类型时，返回联合类型


// 5.extends

// 有时候我们定义的泛型不想过于灵活或者说想继承某些类等，可以通过 extends 关键字添加泛型约束。

 ​
 interface ILengthwise {
   length: number;
 }
 ​
 function loggingIdentity<T extends ILengthwise>(arg: T): T {
   console.log(arg.length);
   return arg;
 }
//  现在这个泛型函数被定义了约束，因此它不再是适用于任意类型：

//  loggingIdentity(3);  // Error, number doesn't have a .length property
// 这时我们需要传入符合约束类型的值，必须包含必须的属性：

//  loggingIdentity({length: 10, value: 3});



// 6.Partial

// Partial 的作用就是将某个类型里的属性全部变为可选项 ?。

// 定义：

//  ​
//  /**
//   * node_modules/typescript/lib/lib.es5.d.ts
//   * Make all properties in T optional
//   */
//  type Partial<T> = {
//    [P in keyof T]?: T[P];
//  };
// 在以上代码中，首先通过 keyof T 拿到 T 的所有属性名，然后使用 in 进行遍历，将值赋给 P，最后通过 T[P] 取得相应的属性值。中间的 ? 号，用于将所有属性变为可选。

// 示例：

 ​
 interface Todo {
   title: string;
   description: string;
 }
 
 ​
 function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
   return { ...todo, ...fieldsToUpdate };
 }
 ​
 const todo1 = {
   title: "organize desk",
   description: "clear clutter",
 };
 ​
 const todo2 = updateTodo(todo1, {
   description: "throw out trash",
 });

// 在上面的 updateTodo 方法中，我们利用 Partial 工具类型，定义 fieldsToUpdate 的类型为 Partial，即：

//  {   title?: string | undefined;   description?: string | undefined;}

